package com.xzz.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.xzz.domain.Course;
import com.xzz.domain.CourseMarket;
import com.xzz.domain.CourseOrder;
import com.xzz.domain.CourseOrderItem;
import com.xzz.dto.*;
import com.xzz.mapper.CourseOrderMapper;
import com.xzz.result.JSONResult;
import com.xzz.service.ICourseOrderItemService;
import com.xzz.service.ICourseOrderService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.xzz.util.AssertUtil;
import com.xzz.util.CodeGenerateUtils;
import com.xzz.vo.OrderInfoVo;
import com.xzz.vo.OrderItemInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import com.xzz.feign.CourseFeignClient;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xzz
 * @since 2022-09-16
 */
@Service
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private ICourseOrderItemService courseOrderItemService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public String placeOrder(OrderParamDto dto) {
        List<Long> courseIds = dto.getCourseIds();
        //集合转string
        String courseIdsStr = StringUtils.join(courseIds, ",");

        //校验token
        Long loginId=3L;
        String key = "token:"+loginId+":"+courseIdsStr;
        Object token = redisTemplate.opsForValue().get(key);
        AssertUtil.isNotNull(token,"token失效");
        AssertUtil.isEquals(dto.getToken(),token.toString(),"token不一致");

        //查询到订单的全部信息，以及状态
        JSONResult jsonResult = courseFeignClient.orderInfo(courseIdsStr);
        AssertUtil.isTrue(jsonResult.isSuccess(),"下单失败！未查询到课程!");

        //查询到订单的全部信息
        OrderInfoVo orderInfoVo = JSON.parseObject(JSON.toJSONString(jsonResult.getData()), OrderInfoVo.class);
        List<OrderItemInfoVo> courseInfos = orderInfoVo.getCourseInfos();

        //填充主从订单数据，也就是订单（整个交易的全部）和订单明细（交易里面的每一个商品）
        //填充主订单（整个交易的全部）
        CourseOrder order = new CourseOrder();
        Date now = new Date();
        order.setCreateTime(now);
        order.setOrderNo(CodeGenerateUtils.generateOrderSn(loginId));
        order.setTotalCount(1);
        order.setStatusOrder(CourseOrder.STATE_WAITE_PAY);
        order.setUserId(loginId);
        order.setPayType(dto.getPayType());
        // 保存主订单数据
        //insert(order);//      保存订单，交给Mq的事务监听器去保存本地事务

        StringBuffer title = new StringBuffer();

        // 填充从订单（交易里面的每一个商品）
        title.append("课程：【");
        for (OrderItemInfoVo courseInfo : courseInfos) {
            Course course = courseInfo.getCourse();
            CourseMarket courseMarket = courseInfo.getCourseMarket();
            CourseOrderItem item = new CourseOrderItem();
            //item.setOrderId(order.getId());   已经没有返回自增长id，交给Mq的事务监听器去保存本地事务
            item.setAmount(courseMarket.getPrice());//每一个课程的单价，不是总价
            item.setCount(1);
            item.setCreateTime(now);
            item.setCourseId(course.getId());
            item.setCourseName(course.getName());
            item.setCoursePic(course.getPic());
            item.setOrderNo(order.getOrderNo());
            // 保存数据
            //courseOrderItemService.insert(item);    交给Mq的事务监听器去保存本地事务
            order.getItems().add(item);
            title.append(course.getName());
            order.setTotalAmount(order.getTotalAmount().add(courseMarket.getPrice()));//累加子订单的钱
            //order.setTotalAmount(orderInfoVo.getTotalAmount());
        }
        title.append("】订单");
        order.setTitle(title.toString());
        //updateById(order);    交给Mq的事务监听器去保存本地事务

        // =======支付相关===   发送事务消息：在发送消息调用支付的时候，同时保存订单
        //这是发送给支付宝的对象
        HashMap<String, Object> map = new HashMap<>();
        map.put("loginId",loginId);
        map.put("courseIds",courseIdsStr);
        Order2PayOrderParamDto paramDto = new Order2PayOrderParamDto(
                order.getTotalAmount(),
                dto.getPayType(),
                order.getOrderNo(),
                loginId,
                JSON.toJSONString(map),//扩展参数
                order.getTitle()
        );
        //构建发送的消息
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(paramDto)).build();
        //生产者发送消息，以及发送本地事务所需要的参数
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "TxOrderGroupListener", //事务监听器组名字
                "topic-order:tag-order", //主题：标签
                message,//消息：用作保存支付单，给别人执行
                order//参数：用作保存课程订单和明细。自己就可以执行，本地事务
        );

        //对比本地事务，以及消息，是否发送成功的状态
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        boolean isSuccess = localTransactionState.equals(LocalTransactionState.COMMIT_MESSAGE) || sendStatus.equals(SendStatus.SEND_OK);
        AssertUtil.isTrue(isSuccess,"下单失败");

        //每个订单都要走一遍超时判断，超过进入死信，另一个消费者去消费死信，去做退库存
        //支付超时取消，发送一个延迟消息到MQ，让订单去关单，支付单去关单且调用支付宝去关单
        SendResult result = rocketMQTemplate.syncSend(
                "topic-paytimeout:tag-paytimeout",
                MessageBuilder.withPayload(order.getOrderNo()).build(),
                //消息延迟级别5，3000为死信时间
                3000,
                5
        );

        AssertUtil.isTrue(result.getSendStatus() == SendStatus.SEND_OK,"延迟消息发送失败");

        // 删除redis中的防重复token
        redisTemplate.delete(key);

        //返回订单号
        return order.getOrderNo();
    }

    /*
     * 保存本地事务，保存订单和子订单
     */
    @Override
    @Transactional
    public void saveOrderAndItem(CourseOrder courseOrder) {
        CourseOrder courseOrderTmp = selectByOrderNo(courseOrder.getOrderNo());
        AssertUtil.isNull(courseOrderTmp,"订单已存在");
        //保存主订单
        insert(courseOrder);
        //批量保存子订单
        List<CourseOrderItem> items = courseOrder.getItems();
        for (CourseOrderItem item : items) {
            item.setOrderId(courseOrder.getId());
        }
        courseOrderItemService.insertBatch(items);
    }

    /*
     * 根据订单号查询
     */
    @Override
    public CourseOrder selectByOrderNo(String orderNo){
        EntityWrapper<CourseOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",orderNo);
        return selectOne(wrapper);
    }

    /*
     * 更新订单的状态以及修改时间
     */
    @Override
    public void updateCourseOrder(PayResultDto dto) {
        CourseOrder courseOrder = selectByOrderNo(dto.getOrderNo());
        courseOrder.setStatusOrder(CourseOrder.STATE_PAY_SUCCESS);
        courseOrder.setUpdateTime(new Date());
        updateById(courseOrder);
    }

    @Override
    public void closeOrder(String orderNo) {
        CourseOrder order = selectByOrderNo(orderNo);
        if (order==null){
            return;
        }
        if(order.getStatusOrder()!=CourseOrder.STATE_WAITE_PAY){
            return;
        }
        log.info("支付超时取消");
        order.setStatusOrder(CourseOrder.STATE_CANCEL);
        updateById(order);
    }

    @Override
    public String killPlaceOrder(KillOrderParamDto dto) {
        //查询预创订单
        Object tmp = redisTemplate.opsForValue().get(dto.getOrderNo());
        AssertUtil.isNotNull(tmp,"下单错误，信息不存在！！！");
        PreOrderDto orderDto = (PreOrderDto)tmp;
        Long loginId = orderDto.getUserId();
        //   拼接key从redis获取token
        String key = "token:"+loginId+":"+orderDto.getCourseId();
        Object tokenTmp = redisTemplate.opsForValue().get(key);
        //     获取不到，直接报错
        AssertUtil.isNotNull(tokenTmp,"token失效，请重新下单");
        //     获取到了，但是比对值不一致，报错
        AssertUtil.isEquals(dto.getToken(),tokenTmp.toString(),"你是不是想搞事！！");
        // 下单====
        // 3.查询多个课程 + 销售
        // 为课程服务编写controller接口 已经写好了，公用 订单结算页渲染接口
        // 为课程服务编写api-course
        // 通过feign调用课程服务，获得课程+销售信息
        JSONResult jsonResult = courseFeignClient.orderInfo(orderDto.getCourseId().toString());
        AssertUtil.isTrue(jsonResult.isSuccess(),"下单失败！未查询到课程!");
        Object data = jsonResult.getData();
        AssertUtil.isNotNull(data,"下单失败！未查询到课程!");
        String voStr = JSON.toJSONString(data);
        OrderInfoVo orderInfoVo = JSON.parseObject(voStr, OrderInfoVo.class);
        List<OrderItemInfoVo> courseInfos = orderInfoVo.getCourseInfos();

        //创建主订单
        // 5.封装主订单
        CourseOrder order = new CourseOrder();
        Date now = new Date();
        order.setCreateTime(now);
        order.setOrderNo(orderDto.getOrderNo());
        order.setTotalCount(1);
        order.setStatusOrder(CourseOrder.STATE_WAITE_PAY);
        order.setUserId(orderDto.getUserId());
        order.setPayType(dto.getPayType());
        order.setTotalAmount(orderDto.getTotalAmount());
        // 6.保存数据
        //insert(order);//保存订单  交给Mq的事务监听器去保存本地事务

        StringBuffer title = new StringBuffer();
        //创建子订单
        title.append("课程：【");
        for (OrderItemInfoVo courseInfo : courseInfos) {
            Course course = courseInfo.getCourse();
            CourseOrderItem item = new CourseOrderItem();
            item.setOrderId(order.getId());
            item.setAmount(orderDto.getTotalAmount());
            item.setCount(1);
            item.setCreateTime(now);
            item.setCourseId(course.getId());
            item.setCourseName(course.getName());
            item.setCoursePic(course.getPic());
            item.setOrderNo(order.getOrderNo());
            // 6.保存数据
            //courseOrderItemService.insert(item); 交给Mq的事务监听器去保存本地事务
            order.getItems().add(item);
            title.append(course.getName());
        }
        title.append("】订单");
        order.setTitle(title.toString());
        //updateById(order);  交给Mq的事务监听器去保存本地事务
        //主订单和子订单都有完整信息了
        // =======支付相关===   发送事务消息，让支付服务消费消息保存支付单
        HashMap<String, Object> map = new HashMap<>();
        map.put("loginId",loginId);
        map.put("courseIds",orderDto.getCourseId());

        Order2PayOrderParamDto paramDto = new Order2PayOrderParamDto(
                order.getTotalAmount(),
                dto.getPayType(),
                order.getOrderNo(),
                loginId,
                JSON.toJSONString(map),//扩展参数
                order.getTitle()
        );

        String jsonString = JSON.toJSONString(paramDto);
        Message<String> message = MessageBuilder.withPayload(jsonString).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "TxOrderGroupListener",
                "topic-order:tag-order",
                message,
                order
        );
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();//本地事务的执行状态
        SendStatus sendStatus = transactionSendResult.getSendStatus();//消息发送状态
        boolean isSuccess = localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK;
        AssertUtil.isFalse(isSuccess,"下单失败！！！");


        //支付超时取消 延迟消息
        SendResult sendResult = rocketMQTemplate.syncSend(
                "topic-paytimeout:tag-paytimeout",
                MessageBuilder.withPayload(order.getOrderNo()).build(),
                3000,
                5
        );

        AssertUtil.isTrue(sendResult.getSendStatus() == SendStatus.SEND_OK,"延迟消息发送失败");

        // 7.删除redis中的防重复token
        redisTemplate.delete(key);

        //8.删除预创单
        redisTemplate.delete(dto.getOrderNo());

        return order.getOrderNo();
    }
}
